package gov.pnnl.stucco.utilities;


import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import gov.pnnl.stucco.collectors.CollectorHttp;
import gov.pnnl.stucco.collectors.Config;
import gov.pnnl.stucco.utilities.CommandLine.UsageException;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.quartz.CronExpression;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Scheduler for Stucco collectors. Read cron specifications from a config map,
 * and schedules collection (using the Quartz library).
 */
public class CollectorScheduler {

    /**
     * Loads the jobs into the Quartz scheduler. Any given entry may be
     * specified to run at startup and/or on a schedule.
     */
    @SuppressWarnings({"unchecked"})
    public void runSchedule(Map<String, Object> collectorsSectionConfig) {
        try {
            Logger log = LoggerFactory.getLogger(CollectorScheduler.class);
            Collection<Object> collectorConfigs = (Collection<Object>) collectorsSectionConfig.get("collectors");
            Scheduler sched = StdSchedulerFactory.getDefaultScheduler();
            sched.start();
            for (Object obj : collectorConfigs) {
                Map<String, Object> collectorConfig = (Map<String, Object>) obj;
                String uri = (String) collectorConfig.get(CollectorHttp.SOURCE_URI);
                String startUp = (String) collectorConfig.get(CollectorHttp.NOW_COLLECT_KEY);
                startUp = (startUp == null) ? "none" : startUp.trim();
                String cronExpr = ((String) collectorConfig.get("cron"));
                cronExpr = (cronExpr == null) ? "" : cronExpr.trim();
                if (!startUp.equalsIgnoreCase("none")) {
                    Map<String, Object> copyConfig = new HashMap<>(collectorConfig);
                    JobDataMap jobData = CollectorJob.convertToJobDataMap(copyConfig);
                    scheduleJob(sched, jobData, uri, "now");
                }

                if (CronExpression.isValidExpression(cronExpr)) {
                    Map<String, Object> copyConfig = new HashMap<>(collectorConfig);
                    copyConfig.remove(CollectorHttp.NOW_COLLECT_KEY);
                    JobDataMap jobData = CollectorJob.convertToJobDataMap(copyConfig);
                    scheduleJob(sched, jobData, uri, cronExpr);
                } else if (cronExpr.isEmpty()) {
                    log.info("URI \"{}\" has no cron expression", uri);
                } else {
                    log.error("URI \"{}\" has invalid cron expression \"{}\"", uri, cronExpr);
                }
            }

            Thread.sleep(Long.MAX_VALUE);
        } catch (SchedulerException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(1);
    }

    /**
     * Schedules a Quartz job/trigger pair.
     *
     * @param sched    Quartz scheduler
     * @param jobData  Any data parameters needed by the job
     * @param name     Name to use for the job and trigger
     * @param cronExpr Quartz-style cron expression or the word "now" (for immediate execution)
     * @throws SchedulerException if unable to schedule
     */
    private void scheduleJob(Scheduler sched, JobDataMap jobData, String name, String cronExpr) throws SchedulerException {
        boolean immediate = cronExpr.equalsIgnoreCase("now");

        // Set up various identifiers
        String jobName = name;
        String triggerName = name;
        String groupName = immediate ? "startup" : "cron";

        // Define the job
        JobDetail job = newJob(CollectorJob.class)
                .withIdentity(jobName, groupName)
                .setJobData(jobData)
                .build();

        // Shared part of trigger set up
        TriggerBuilder<Trigger> builder = newTrigger()
                .withIdentity(triggerName, groupName)
                .startNow();

        // Build the trigger
        Trigger trigger = null;
        if (immediate) {
            //... for immediate running
            trigger = builder.build();
        } else if (CronExpression.isValidExpression(cronExpr)) {
            //... for a cron schedule
            trigger = builder.withSchedule(cronSchedule(cronExpr)).build();
        }

        sched.scheduleJob(job, trigger);
    }

    public static void main(String[] args) {
        try {
            CommandLine parser = new CommandLine();
            parser.add1("-file");
            parser.add1("-section");
            parser.parse(args);
            for(String s:args)
            System.out.println(s);
            if (parser.found("-file")) {
                String configFilename = parser.getValue();
                Config.setConfigFile(new File(configFilename));
            } else {
                throw new UsageException("-file switch is required");
            }
            String section = "scheduler";
            if (parser.found("-section")) {
                section = parser.getValue();
            }
            Map<String, Object> config = Config.getMap();
            Map<String, Object> sectionMap = (Map<String, Object>) config.get(section);
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    CollectorMetadata metadata = CollectorMetadata.getInstance();
                    synchronized (metadata) {
                    }
                }
            });
            CollectorScheduler scheduler = new CollectorScheduler();
            scheduler.runSchedule(sectionMap);
        } catch (UsageException e) {
            System.err.println("Usage: CollectorScheduler -file configFile [-section section]");
            System.exit(1);
        }
    }

}
